package com.fwtai.tool;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.io.Serializable;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Function;

/**
 * jwt(JSON Web Token)令牌工具类,采用非对称公钥密钥(私钥加密|公钥解密)
 * @作者 田应平
 * @版本 v1.0
 * @创建时间 2020-02-12 23:53
 * @QQ号码 444141300
 * @Email service@yinlz.com
 * @官网 <url>http://www.fwtai.com</url>
*/
public final class ToolJWT implements Serializable{

  //如设置Token过期时间15分钟，建议更换时间设置为Token前5分钟,通过try catch 获取过期
  private final static long access_token = 1000 * 60 * 4500;//当 refreshToken 已过期了，再判断 accessToken 是否已过期,

  /**一般更换新的accessToken小于5分钟则提示需要更换新的accessToken*/
  private final static long refresh_token = 1000 * 60 * 4000;//仅做token的是否需要更换新的accessToken标识,小于5分钟则提示需要更换新的accessToken

  private final static long app_access_token = 1000 * 60 * 60 * 24 * 20L;//20天

  private final static long app_refresh_token = 1000 * 60 * 60 * 24 * 7L;//7天

  private final static String issuer = "富翁泰(www.fwtai.com)";//jwt签发者

  /**2048的密钥位的公钥*/
  private final static String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgKdY1ZEFYqh49h2xxyQUNNTO4btcCFZM+eJJmpITY/O0eDsT5hXLSji18ngjtNL3dUMk8PXUPhKp/FOiM+uc4UB0JtCK9O0szwiKDlMKO016xb8Mc5gJx8sPu71F4mM+SgChquG38wUVaHLTYfFZ6AUYS5VBmGb6H46RToIT3ZLnUlITHAKzcHJDYUrtnM7UoxHS7feocCdqre2HycjnXgiA3QLXtvbh1hhVtFdfdKu22xMKWfwb3c9PqR4J3ody7XhgNTJxwwQJ0cMXRDMzgafQ/qz+Lm75Y3I/RPGFdfxEswoDt8FUiODwhlXPji+cmn2PuXq5AW0SbsOU+iktAQIDAQAB";
  /**2048的密钥位的私钥*/
  private final static String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCAp1jVkQViqHj2HbHHJBQ01M7hu1wIVkz54kmakhNj87R4OxPmFctKOLXyeCO00vd1QyTw9dQ+Eqn8U6Iz65zhQHQm0Ir07SzPCIoOUwo7TXrFvwxzmAnHyw+7vUXiYz5KAKGq4bfzBRVoctNh8VnoBRhLlUGYZvofjpFOghPdkudSUhMcArNwckNhSu2cztSjEdLt96hwJ2qt7YfJyOdeCIDdAte29uHWGFW0V190q7bbEwpZ/Bvdz0+pHgneh3LteGA1MnHDBAnRwxdEMzOBp9D+rP4ubvljcj9E8YV1/ESzCgO3wVSI4PCGVc+OL5yafY+5erkBbRJuw5T6KS0BAgMBAAECggEAKJ0rxRfn/FI1BXUwVy6Dln4L5WTxU8xytnwCQmKU7nvT4BS+XmdI6uBmR+o3U3E6H3GX6t2gKAxaT0bTsNJTjyR6xnoVM+bfu2f1tTnvw/LcrCp4stEvXTw0dk71DdfIpTH312s0V+ql7Hg7VPMNL9rT5aBzubnggi/OnVshDFRMxuPOJvWbx4fn2m+iC3YoDvyWIsA1wnRCqVyo6w+D7reI0BaUBXqGvgHrDSyN7gu+SI9r15qDPlMwzQl+6uGdbftG9bpu0Ho+98DTXoLydk5NN9BMDFz6m3WbKdiFHvePY8mzCyoP75rtx4bIJeFpglMdfOrI9h4jKkvlIyuxMQKBgQDVrec5y/4QWAxvajRLRqNzZUN9YIUsoFfq8MOSWK4tguLpoFpbjB/wFca/5XinsBdnvPe3PBHmTzY58Edtv1turOPYXJot77a0zP/az0XEBuQCMis/3GghunR2O0IiDP2oHXlsDaXu80u0mB+kFgMADKKvvnJA1c4eHoN7HP9PZQKBgQCaImyX0kRT8FkQ4HaWH2BIkQLlcUFtiWEJjL3VbAzxFuf5tjNN82YIB5ug+vy5DB2qa2i3pARVACvJQw/AlRyYWJOUHujpANyD7Wp9g3zJD+uxuBvJIHtkEl0Ephr0Gjo2I0rYBZwCEQmK2DEZwSKb1vauVjlcCKOgzD8q6gJzbQKBgH6kVRjCOFADf0tBTMEYmqM37yoN3KA4YCMUkzTXblP28Pm6WhNMF1m57lOpczsfeJ+5Na/Z4Fn5tI/eQtdexJfxzBBdwdtb6ZN6tGFA2350/wn2Jf/xZ54l3gR81pNL08KhxWcvJ5tZgxMD3izoBtsuofU7EIgwr7rWJuL+qqqpAoGAKcO2JRVGinAP0jDgMSFjV4ZjgIRFtEaLUqkeLxxODW2lgdZK5I5VUUuucdpQbFvKFglwoIIC4n1fX/OZ6xb8XpNxuar+dVRUbDMCrevY/iogNVGrxb3VcXJAXigcGZSZvV2mxA4RDcZMDaEy/MWGeS+wO+EFPdjhbYqqexhZfakCgYEAxa2RS1F2RCmQF2Hkxa5A2CCrjJCqVulQ+lNRgu2GSEsI4D/uuzyY1Y+Phm5W2ZDTAO91JrJdadhvGaazQ854CbmQ+7BGJD/SJkAR/3nHTaBB7Th2XF7toqHC4qSZoDEbes5Qy828UwMfctXIUzWBeX+ygSpVNeJIhZDpSik05ns=";

  /**java生成的私钥是pkcs8格式的公钥是x.509格式*/
  private static PublicKey getPublicKey(){
    try {
      final byte[] keyBytes = Base64.getDecoder().decode(publicKey);
      final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
      final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
      return keyFactory.generatePublic(keySpec);
    } catch (final Exception e){
      e.printStackTrace();
    }
    return null;
  }

  private static PrivateKey getPrivateKey(){
    try {
      final byte[] keyBytes = Base64.getDecoder().decode(privateKey);
      final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
      final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
      return keyFactory.generatePrivate(keySpec);
    } catch (final Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  // setSubject 不能和setClaims() 同时使用,如果用不到 userId() 的话可以把setId的值设为 userName !!!
  private static String createToken(final String userId,final Object value,final long expiryDate){
    final ExecutorService threadPool = Executors.newCachedThreadPool();
    final Future<String> future = threadPool.submit(new Callable<String>(){
      @Override
      public String call() throws Exception{
        final long date = System.currentTimeMillis();
        final JwtBuilder builder = Jwts.builder().signWith(getPrivateKey(),SignatureAlgorithm.RS384);
        if(value !=null){
          builder.claim(userId,value);
        }
        return builder.setId(userId).setIssuer(issuer).setIssuedAt(new Date(date)).setExpiration(new Date(date + expiryDate)).compact();
      }
    });
    try {
      return future.get();
    } catch (final Exception e) {
      return null;
    }finally{
      threadPool.shutdown();
    }
  }

  private static String createToken(final String userId,final Object value,final HashMap<String,Object> claims,final long expiryDate){
    final ExecutorService threadPool = Executors.newCachedThreadPool();
    final Future<String> future = threadPool.submit(new Callable<String>(){
      @Override
      public String call() throws Exception{
        final long date = System.currentTimeMillis();
        final JwtBuilder builder = Jwts.builder().signWith(getPrivateKey(),SignatureAlgorithm.RS384);
        if(value != null){
          builder.claim(userId,value);
        }
        if(claims !=null && claims.size()>0){
          for(final String key : claims.keySet()){
            builder.claim(key,claims.get(key));
          }
        }
        return builder.setId(userId).setIssuer(issuer).setIssuedAt(new Date(date)).setExpiration(new Date(date + expiryDate)).compact();
      }
    });
    try {
      return future.get();
    } catch (final Exception e) {
      return null;
    }finally{
      threadPool.shutdown();
    }
  }

  public static Claims parser(final String token){
    return Jwts.parserBuilder().requireIssuer(issuer).setSigningKey(getPublicKey()).build().parseClaimsJws(token).getBody();
  }

  /**
   * 验证token是否已失效,返回true已失效,否则有效
   * @param token
   * @作者 田应平
   * @QQ 444141300
   * @创建时间 2020年2月24日 16:19:00
   */
  public static boolean tokenExpired(final String token) {
    try {
      return parser(token).getExpiration().before(new Date());
    } catch (final ExpiredJwtException exp) {
      return true;
    }
  }

  public static boolean validateToken(final String token,final String userId){
    final String uid = extractUserId(token);
    return (uid.equals(userId) && !tokenExpired(token));
  }

  /**仅作为是否需要刷新的accessToken标识,不做任何业务处理*/
  public static String expireRefreshToken(final String userId){
    return createToken(userId,null,refresh_token);
  }

  /**生成带认证实体且有权限的token,最后个参数是含List<String>的角色信息*/
  public static String expireAccessToken(final String userId){
    return createToken(userId,null,access_token);
  }

  public static String buildRefreshToken(final String userId){
    return createToken(userId,null,app_refresh_token);
  }

  public static String buildAccessToken(final String userId){
    return createToken(userId,null,app_access_token);
  }

  /**其实id就是本项目的userId*/
  public static String getId(final String token){
    return parser(token).getId();
  }

  /**生成带认证实体且有权限的token,附加参数,如区域等级*/
  public static String expireAccessToken(final String userId,final HashMap<String,Object> claims){
    return createToken(userId,null,claims,access_token);
  }

  private static <T> T extractObjet(final String token,final Function<Claims,T> claimsResolver){
    final Claims claims = parser(token);
    return claimsResolver.apply(claims);
  }

  public static String extractUserId(final String token){
    return extractObjet(token,Claims::getId);
    /* ok的
      return extractObjet(token,(claims -> {
        return claims.getId();
      }));
    */
    //return extractObjet(token,(claims -> claims.getId()));ok的
  }

  //指定key获取对应的value
  private static Object extractKey(final String token,final String claimKey){
    final Claims claims = parser(token);
    return claims.get(claimKey);
  }

  public static Object getClaimKey(final String token,final String claimKey){
    return extractKey(token,claimKey);
  }
}